Laboratorio No. 2 - Regresión Lineal

Maria José Castro Lemus
Paula Camila González Ortega

Adjunto a este NoteBook encontrarán el archivo Autos-csv que contiene datos sobre automóviles de segunda mano. El objetivo principal de este ejercicio es desarrollar un modelo que permita, en base a las características (features) que tiene el archivo, predecir el costo de un vehículo usado.

A diferencia de la mayoría de ejercicios, este archivo no ha sido depurado, es decir los datos son crudos (raw). Esto quiere decir que antes de trabajar sobre un modelo predictivo, deben realizar el proceso total, desde limpieza de datos, exploración inicial, etc.

A continuación encontrarán que hay encabezados que bien podrían ayudarlos a desarrollar el código requerido. Estos están a manera de sugerencia únicamente y Ustedes decidirán si los usan, modifican, eliminan, agregan a los mismos

Importar las librerías relevantes

In [7]:
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns

from quickda.explore_data import *
from quickda.clean_data import *
from quickda.explore_numeric import *
from quickda.explore_categoric import *
from quickda.explore_numeric_categoric import *
from quickda.explore_time_series import *
%matplotlib inline

Cargar los datos crudos

In [2]:
raw_df = pd.read_csv('Autos.csv')
In [3]:
raw_df.head()
Out[3]:
Brand Price Body Mileage EngineV Engine Type Registration Year Model
0 BMW 4200.0 sedan 277 2.0 Petrol yes 1991 320
1 Mercedes-Benz 7900.0 van 427 2.9 Diesel yes 1999 Sprinter 212
2 Mercedes-Benz 13300.0 sedan 358 5.0 Gas yes 2003 S 500
3 Audi 23000.0 crossover 240 4.2 Petrol yes 2007 Q7
4 Toyota 18300.0 crossover 120 2.0 Petrol yes 2011 Rav 4
In [4]:
raw_df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 4345 entries, 0 to 4344
Data columns (total 9 columns):
 #   Column        Non-Null Count  Dtype  
---  ------        --------------  -----  
 0   Brand         4345 non-null   object 
 1   Price         4173 non-null   float64
 2   Body          4345 non-null   object 
 3   Mileage       4345 non-null   int64  
 4   EngineV       4195 non-null   float64
 5   Engine Type   4345 non-null   object 
 6   Registration  4345 non-null   object 
 7   Year          4345 non-null   int64  
 8   Model         4345 non-null   object 
dtypes: float64(2), int64(2), object(5)
memory usage: 305.6+ KB

Preprocesamiento

Explorar las estadísticas descriptivas de las variables

In [5]:
raw_df.describe()
Out[5]:
Price Mileage EngineV Year
count 4173.000000 4345.000000 4195.000000 4345.000000
mean 19418.746935 161.237284 2.790734 2006.550058
std 25584.242620 105.705797 5.066437 6.719097
min 600.000000 0.000000 0.600000 1969.000000
25% 6999.000000 86.000000 1.800000 2003.000000
50% 11500.000000 155.000000 2.200000 2008.000000
75% 21700.000000 230.000000 3.000000 2012.000000
max 300000.000000 980.000000 99.990000 2016.000000
In [8]:
explore(raw_df, method="summarize") 
Out[8]:
dtypes count null_sum null_pct nunique min 25% 50% 75% max mean median std skew
Body object 4345 0 0.000 6 crossover - - - van - - - -
Brand object 4345 0 0.000 7 Audi - - - Volkswagen - - - -
Engine Type object 4345 0 0.000 4 Diesel - - - Petrol - - - -
EngineV float64 4195 150 0.035 73 0.6 1.8 2.2 3.0 99.99 2.790734 2.2 5.066437 16.890604
Mileage int64 4345 0 0.000 410 0 86.0 155.0 230.0 980 161.237284 155.0 105.705797 0.808364
Model object 4345 0 0.000 312 1 Series - - - Z4 - - - -
Price float64 4173 172 0.040 906 600.0 6999.0 11500.0 21700.0 300000.0 19418.746935 11500.0 25584.24262 4.45208
Registration object 4345 0 0.000 2 no - - - yes - - - -
Year int64 4345 0 0.000 39 1969 2003.0 2008.0 2012.0 2016 2006.550058 2008.0 6.719097 -0.951043
In [9]:
sns.pairplot(raw_df)
Out[9]:
<seaborn.axisgrid.PairGrid at 0x2af76566b80>

Determinación de las variables de interés

In [10]:
raw_df.columns
Out[10]:
Index(['Brand', 'Price', 'Body', 'Mileage', 'EngineV', 'Engine Type',
       'Registration', 'Year', 'Model'],
      dtype='object')
In [11]:
X = raw_df[['Brand','Body', 'Mileage', 'EngineV', 'Engine Type','Year']]
y = raw_df['Price']
print(X,y)
              Brand       Body  Mileage  EngineV Engine Type  Year
0               BMW      sedan      277      2.0      Petrol  1991
1     Mercedes-Benz        van      427      2.9      Diesel  1999
2     Mercedes-Benz      sedan      358      5.0         Gas  2003
3              Audi  crossover      240      4.2      Petrol  2007
4            Toyota  crossover      120      2.0      Petrol  2011
...             ...        ...      ...      ...         ...   ...
4340  Mercedes-Benz      sedan        9      3.0      Diesel  2014
4341            BMW      sedan        1      3.5      Petrol  1999
4342            BMW      sedan      194      2.0      Petrol  1985
4343         Toyota      sedan       31      NaN      Petrol  2014
4344     Volkswagen        van      124      2.0      Diesel  2013

[4345 rows x 6 columns] 0         4200.0
1         7900.0
2        13300.0
3        23000.0
4        18300.0
          ...   
4340    125000.0
4341      6500.0
4342      8000.0
4343     14200.0
4344     13500.0
Name: Price, Length: 4345, dtype: float64
In [12]:
eda_numcat(raw_df, "Price", 
           method = "pps") 
Feature Importance in the prediction of Price

Manejo de valores faltantes

In [13]:
sns.heatmap(raw_df.isnull(),yticklabels=False,cbar=False,cmap='viridis')
Out[13]:
<matplotlib.axes._subplots.AxesSubplot at 0x2af00524cd0>
In [14]:
raw_df['EngineV'].isna().sum()
Out[14]:
150
In [15]:
raw_df['Price'].isna().sum()
Out[15]:
172
In [16]:
# se les pone el precio promedio
def imputar_precios(cols):
    precio = cols[0]
    
    if pd.isnull(precio):
            return 19418.74
    else:
        return precio
In [17]:
# se les pone el enginev promedio
def imputar_enginev(cols):
    enginev = cols[0]
    
    if pd.isnull(enginev):
            return 2.79
    else:
        return enginev
In [18]:
raw_df['Price'] = raw_df[['Price']].apply(imputar_precios,axis=1) #axis 1 es columnas
In [19]:
raw_df['EngineV'] = raw_df[['EngineV']].apply(imputar_enginev,axis=1) #axis 1 es columnas
In [20]:
sns.heatmap(raw_df.isnull(),yticklabels=False,cbar=False,cmap='viridis')
Out[20]:
<matplotlib.axes._subplots.AxesSubplot at 0x2af0013e4c0>

Exploración de las Funciones de Distribución de Probabilidades (PDFs por sus siglas en inglés)

In [21]:
raw_df['Price'].hist(color='green',bins=40,figsize=(8,4))
Out[21]:
<matplotlib.axes._subplots.AxesSubplot at 0x2af00858070>
In [22]:
raw_df['Mileage'].hist(color='green',bins=40,figsize=(8,4))
#Descartada por su distribucion
Out[22]:
<matplotlib.axes._subplots.AxesSubplot at 0x2af0091a0a0>
In [23]:
raw_df['Year'].hist(color='green',bins=40,figsize=(8,4))
#Descartada por su distribucion
Out[23]:
<matplotlib.axes._subplots.AxesSubplot at 0x2af00c9b130>
In [24]:
raw_df['EngineV'].hist(color='green',bins=40,figsize=(8,4))
Out[24]:
<matplotlib.axes._subplots.AxesSubplot at 0x2af00d645b0>

Manejo de valores atípicos (outliers)

In [25]:
plt.figure(figsize=(15, 7))
sns.boxplot(x='Brand',y='Price',data=raw_df,palette='winter')
Out[25]:
<matplotlib.axes._subplots.AxesSubplot at 0x2af7fabfd00>
In [26]:
plt.figure(figsize=(15, 7))
sns.boxplot(x='Body',y='Price',data=raw_df,palette='winter')
Out[26]:
<matplotlib.axes._subplots.AxesSubplot at 0x2af00ec0ca0>
In [27]:
plt.figure(figsize=(15, 7))
sns.boxplot(x='Engine Type',y='Price',data=raw_df,palette='winter')
Out[27]:
<matplotlib.axes._subplots.AxesSubplot at 0x2af00f80100>
In [30]:
raw_df = clean(raw_df, method="outliers", 
      columns=[])   

Revisando los supuestos para OLS

In [31]:
import statsmodels.api as sm
y = raw_df['Price']
x1 = raw_df[['Mileage', 'EngineV', 'Year']]
x = sm.add_constant(x1)
results = sm.OLS(y,x).fit()
results.summary()
c:\users\camila\appdata\local\programs\python\python38\lib\site-packages\statsmodels\tsa\tsatools.py:142: FutureWarning:

In a future version of pandas all arguments of concat except for the argument 'objs' will be keyword-only

Out[31]:
OLS Regression Results
Dep. Variable: Price R-squared: 0.556
Model: OLS Adj. R-squared: 0.556
Method: Least Squares F-statistic: 1501.
Date: Sun, 08 Aug 2021 Prob (F-statistic): 0.00
Time: 11:12:39 Log-Likelihood: -36244.
No. Observations: 3595 AIC: 7.250e+04
Df Residuals: 3591 BIC: 7.252e+04
Df Model: 3
Covariance Type: nonrobust
coef std err t P>|t| [0.025 0.975]
const -1.621e+06 4.57e+04 -35.500 0.000 -1.71e+06 -1.53e+06
Mileage -17.3219 1.446 -11.980 0.000 -20.157 -14.487
EngineV 5263.7272 150.309 35.019 0.000 4969.027 5558.427
Year 810.0574 22.667 35.738 0.000 765.617 854.498
Omnibus: 493.350 Durbin-Watson: 2.060
Prob(Omnibus): 0.000 Jarque-Bera (JB): 802.163
Skew: 0.935 Prob(JB): 6.49e-175
Kurtosis: 4.364 Cond. No. 9.53e+05


Notes:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.
[2] The condition number is large, 9.53e+05. This might indicate that there are
strong multicollinearity or other numerical problems.
In [32]:
#matriz de correlación
sns.heatmap(raw_df.corr(), annot = True)
Out[32]:
<matplotlib.axes._subplots.AxesSubplot at 0x2af00eb7c10>

Multicolinealidad

In [33]:
from statsmodels.stats.outliers_influence import variance_inflation_factor

def calc_vif(X):

    # Calculating VIF
    vif = pd.DataFrame()
    vif["variables"] = X.columns
    vif["VIF"] = [variance_inflation_factor(X.values, i) for i in range(X.shape[1])]

    return(vif)
In [34]:
X = raw_df[['Mileage', 'EngineV', 'Year']]
calc_vif(X)
Out[34]:
variables VIF
0 Mileage 4.367485
1 EngineV 13.327293
2 Year 14.916342

Crear variables comodín (Dummy)

In [35]:
marca = pd.get_dummies(raw_df['Brand'], drop_first=True)
marca
Out[35]:
BMW Mercedes-Benz Mitsubishi Renault Toyota Volkswagen
0 1 0 0 0 0 0
1 0 1 0 0 0 0
3 0 0 0 0 0 0
4 0 0 0 0 1 0
7 0 0 0 0 0 0
... ... ... ... ... ... ...
4338 0 0 0 0 0 1
4339 0 0 0 0 1 0
4341 1 0 0 0 0 0
4343 0 0 0 0 1 0
4344 0 0 0 0 0 1

3595 rows × 6 columns

In [36]:
tipo = pd.get_dummies(raw_df['Body'], drop_first=True)
tipo
Out[36]:
hatch other sedan vagon van
0 0 0 1 0 0
1 0 0 0 0 1
3 0 0 0 0 0
4 0 0 0 0 0
7 0 0 0 1 0
... ... ... ... ... ...
4338 0 0 0 0 1
4339 0 0 1 0 0
4341 0 0 1 0 0
4343 0 0 1 0 0
4344 0 0 0 0 1

3595 rows × 5 columns

In [37]:
combustible = pd.get_dummies(raw_df['Engine Type'], drop_first=True)
combustible
Out[37]:
Gas Other Petrol
0 0 0 1
1 0 0 0
3 0 0 1
4 0 0 1
7 0 0 0
... ... ... ...
4338 0 0 0
4339 0 0 1
4341 0 0 1
4343 0 0 1
4344 0 0 0

3595 rows × 3 columns

Re-ordenar un poco

In [38]:
train = pd.concat([raw_df,marca,tipo,combustible],axis=1) #le agregamos las columnas dummies que creamos antes
train.head()
Out[38]:
Brand Price Body Mileage EngineV Engine Type Registration Year Model BMW ... Toyota Volkswagen hatch other sedan vagon van Gas Other Petrol
0 BMW 4200.0 sedan 277 2.0 Petrol yes 1991 320 1 ... 0 0 0 0 1 0 0 0 0 1
1 Mercedes-Benz 7900.0 van 427 2.9 Diesel yes 1999 Sprinter 212 0 ... 0 0 0 0 0 0 1 0 0 0
3 Audi 23000.0 crossover 240 4.2 Petrol yes 2007 Q7 0 ... 0 0 0 0 0 0 0 0 0 1
4 Toyota 18300.0 crossover 120 2.0 Petrol yes 2011 Rav 4 0 ... 1 0 0 0 0 0 0 0 0 1
7 Audi 14200.0 vagon 200 2.7 Diesel yes 2006 A6 0 ... 0 0 0 0 0 1 0 0 0 0

5 rows × 23 columns

In [39]:
train.columns 
Out[39]:
Index(['Brand', 'Price', 'Body', 'Mileage', 'EngineV', 'Engine Type',
       'Registration', 'Year', 'Model', 'BMW', 'Mercedes-Benz', 'Mitsubishi',
       'Renault', 'Toyota', 'Volkswagen', 'hatch', 'other', 'sedan', 'vagon',
       'van', 'Gas', 'Other', 'Petrol'],
      dtype='object')

Modelo de regresión lineal

Declarar las entradas y las metas

In [40]:
#X = raw_df[['Brand','Body', 'Mileage', 'EngineV', 'Engine Type','Year']]
X = train[['Mileage', 'EngineV','Year', 'BMW', 'Mercedes-Benz', 'Mitsubishi',
       'Renault', 'Toyota', 'Volkswagen', 'hatch', 'other', 'sedan', 'vagon',
       'van', 'Gas', 'Other', 'Petrol']]
#X = train[['Mileage', 'EngineV','Year']]
y = train['Price']
print(X,y)
      Mileage  EngineV  Year  BMW  Mercedes-Benz  Mitsubishi  Renault  Toyota  \
0         277     2.00  1991    1              0           0        0       0   
1         427     2.90  1999    0              1           0        0       0   
3         240     4.20  2007    0              0           0        0       0   
4         120     2.00  2011    0              0           0        0       1   
7         200     2.70  2006    0              0           0        0       0   
...       ...      ...   ...  ...            ...         ...      ...     ...   
4338      163     2.50  2008    0              0           0        0       0   
4339       35     1.60  2014    0              0           0        0       1   
4341        1     3.50  1999    1              0           0        0       0   
4343       31     2.79  2014    0              0           0        0       1   
4344      124     2.00  2013    0              0           0        0       0   

      Volkswagen  hatch  other  sedan  vagon  van  Gas  Other  Petrol  
0              0      0      0      1      0    0    0      0       1  
1              0      0      0      0      0    1    0      0       0  
3              0      0      0      0      0    0    0      0       1  
4              0      0      0      0      0    0    0      0       1  
7              0      0      0      0      1    0    0      0       0  
...          ...    ...    ...    ...    ...  ...  ...    ...     ...  
4338           1      0      0      0      0    1    0      0       0  
4339           0      0      0      1      0    0    0      0       1  
4341           0      0      0      1      0    0    0      0       1  
4343           0      0      0      1      0    0    0      0       1  
4344           1      0      0      0      0    1    0      0       0  

[3595 rows x 17 columns] 0        4200.0
1        7900.0
3       23000.0
4       18300.0
7       14200.0
         ...   
4338    11500.0
4339    17900.0
4341     6500.0
4343    14200.0
4344    13500.0
Name: Price, Length: 3595, dtype: float64

Escalar (poner a escala) los datos

In [41]:
from sklearn.preprocessing import StandardScaler
escalador = StandardScaler()
escalador.fit(X)
cols_std = escalador.transform(X)
print(cols_std)
[[ 1.17238082 -0.39854966 -2.59425685 ... -0.41583658 -0.19058255
   1.38066098]
 [ 2.79379693  0.98867379 -1.2393961  ... -0.41583658 -0.19058255
  -0.72429077]
 [ 0.77243151  2.99244101  0.11546466 ... -0.41583658 -0.19058255
   1.38066098]
 ...
 [-1.81102483  1.91348943 -1.2393961  ... -0.41583658 -0.19058255
   1.38066098]
 [-1.4867416   0.81912426  1.30096783 ... -0.41583658 -0.19058255
   1.38066098]
 [-0.48146362 -0.39854966  1.13161023 ... -0.41583658 -0.19058255
  -0.72429077]]
In [42]:
type(cols_std)
Out[42]:
numpy.ndarray
In [43]:
df_std = pd.DataFrame(cols_std, columns=X.columns)
df_std.head()
Out[43]:
Mileage EngineV Year BMW Mercedes-Benz Mitsubishi Renault Toyota Volkswagen hatch other sedan vagon van Gas Other Petrol
0 1.172381 -0.398550 -2.594257 2.514317 -0.458735 -0.312586 -0.379225 -0.378265 -0.562894 -0.280371 -0.32529 1.255076 -0.349836 -0.460067 -0.415837 -0.190583 1.380661
1 2.793797 0.988674 -1.239396 -0.397722 2.179908 -0.312586 -0.379225 -0.378265 -0.562894 -0.280371 -0.32529 -0.796765 -0.349836 2.173597 -0.415837 -0.190583 -0.724291
2 0.772432 2.992441 0.115465 -0.397722 -0.458735 -0.312586 -0.379225 -0.378265 -0.562894 -0.280371 -0.32529 -0.796765 -0.349836 -0.460067 -0.415837 -0.190583 1.380661
3 -0.524701 -0.398550 0.792895 -0.397722 -0.458735 -0.312586 -0.379225 2.643651 -0.562894 -0.280371 -0.32529 -0.796765 -0.349836 -0.460067 -0.415837 -0.190583 1.380661
4 0.340054 0.680402 -0.053893 -0.397722 -0.458735 -0.312586 -0.379225 -0.378265 -0.562894 -0.280371 -0.32529 -0.796765 2.858482 -0.460067 -0.415837 -0.190583 -0.724291

Divisón del df completo en datos de entrenamiento y de prueba (Train Test Split)

In [44]:
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, 
                                                    train['Price'], test_size=0.30, 
                                                    random_state=101)
# X son todos menos price y Y es solo la columna price

Crear la regresión

In [45]:
from sklearn.linear_model import LinearRegression
lm = LinearRegression()
lm.fit(X_train,y_train)
Out[45]:
LinearRegression()
In [46]:
predicciones = lm.predict(X_test)

Encontrar R^2 del modelo

In [47]:
from sklearn.metrics import r2_score
r2 = r2_score(y_test,predicciones)
print(r2)
0.6194923536235666

Nótese que este no es el R^2 ajustado. En otras palabras hay que encontrar el R^2 ajustado para tener una medida más adecuada

Fórmula para R^2 Ajustado

Rajust.2=1(1R2)n1np1

n = número de observaciones

p = número de predictores

In [48]:
p = len(predicciones)
n = X.shape[1]
r2_ajustado = 1 - (1-r2)*((n-1)/(n-p-1))
print(r2_ajustado)
1.0057273022972935

Encontrar los pesos (coeficientes) y el sesgo (intercepto)

In [49]:
coeficientes = pd.DataFrame(lm.coef_, X.columns, columns=['Coeficiente'])
coeficientes
Out[49]:
Coeficiente
Mileage -13.335633
EngineV 2455.812482
Year 860.113389
BMW 1059.256057
Mercedes-Benz -856.504279
Mitsubishi -5746.019830
Renault -6873.336774
Toyota -2055.276442
Volkswagen -2509.656816
hatch -5661.110972
other -3118.506364
sedan -4292.413665
vagon -4776.236388
van -5076.137753
Gas -51.701780
Other -986.363870
Petrol 454.979408
In [50]:
# imprimir la intercepción (constante)
print(lm.intercept_)
-1709981.5668795938

Prueba del modelo

In [56]:
plt.xlim(0, 45000)
plt.ylim(0, 45000)
plt.scatter(y_test,predicciones)
Out[56]:
<matplotlib.collections.PathCollection at 0x2af060f2e20>
In [57]:
sns.displot((y_test - predicciones),bins=50);
In [58]:
from sklearn import metrics
print('MAE:', metrics.mean_absolute_error(y_test, predicciones))
print('MSE:', metrics.mean_squared_error(y_test, predicciones))
print('RMSE:', np.sqrt(metrics.mean_squared_error(y_test, predicciones)))
MAE: 3924.6304773446395
MSE: 28443070.634919874
RMSE: 5333.204537135237

Conclusiones

  • La marca, el tipo, el millaje, los cilindros, el tipo de combustible y año son factores que afectan linealmente el precio de un carro

  • El año y millaje son variables con alta multicolinealidad por lo que el valor de una tiene impacto en la otra

  • Al evaluar las métricas de errores para la regresión lineal se determinó que estas se encuentran dentro del estándar y por lo tanto se realizó correctamente el training de la data y posteriormente la predicción